package uk.ac.cam.echo.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.sql.SQLException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import uk.ac.cam.echo.SafeMessageQueue;
import uk.ac.cam.echo.messages.CheckInMessage;
import uk.ac.cam.echo.messages.FriendCheckInMessage;
import uk.ac.cam.echo.messages.FriendListMessage;
import uk.ac.cam.echo.messages.FriendRequestMessage;
import uk.ac.cam.echo.messages.LocationTableMessage;
import uk.ac.cam.echo.messages.LoginMessage;
import uk.ac.cam.echo.messages.PendingFriendRequestsMessage;
import uk.ac.cam.echo.messages.Message;
import uk.ac.cam.echo.messages.RFIDMessage;
import uk.ac.cam.echo.messages.ScheduleMessage;
import uk.ac.cam.echo.messages.SecurityMessage;
import uk.ac.cam.echo.messages.UserMessage;
import uk.ac.cam.echo.messages.UserTableMessage;

/**
 * @author Tom Smith
 */
public class ClientMessageReader implements Runnable {

	private final Database m_Database = Database.getInstance();
	private final SafeMessageQueue<Message> m_Messages;
	private final Socket m_Socket;

	/**
	 * Initiate object with the socket to listen on.
	 * @param sock The socket to listen on.
	 * @param messageQueue The message queue to write to the client.
	 */
	public ClientMessageReader(ClientHandler handler, Socket sock, SafeMessageQueue<Message> messageQueue) {
		this.m_Socket = sock;
		this.m_Messages = messageQueue;
	}

	/**
	 * Decode a message from the client, and act accordingly.
	 * @param m The message to decode.
	 * @throws SQLException
	 */
	private void decodeMessage(Message m) throws SQLException {

		// Different types of Message we could receive, act accordingly.

		if (m instanceof LoginMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new LoginMessage.");
			// Set user's FacebookID.
			this.m_Database.setFacebookID(m.getRFID(), ((LoginMessage) m).getFacebookID());
			// TODO: WelcomeMessage

		} else if (m instanceof ScheduleMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new ScheduleMessage.");
			// Get schedule, set it in object, and send it back.
			ScheduleMessage s = (ScheduleMessage)m;
			s.setSchedule(this.m_Database.getSchedule(s.getAmount(), s.getLocationRFID()));

			if (s != null)
				sendMessage(s);

		} else if (m instanceof UserMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new UserMessage.");
			// Get user details, set it in object, and send it back.
			UserMessage u = this.m_Database.getUserDetails(m.getRFID());

			if (u != null)
				sendMessage(u);

		} else if (m instanceof FriendRequestMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new FriendRequestMessage.");

			FriendRequestMessage f = (FriendRequestMessage)m;
			// If not already friends.
			String from = f.getRFID();
			String to = f.getToUserID();
			if (! this.m_Database.areFriends(from, to)) {
				// Add value into Friend table.
				this.m_Database.addFriendInstance(from, to);
			}

		} else if (m instanceof FriendListMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new FriendListMessage.");

			FriendListMessage f = (FriendListMessage)m;
			List<UserMessage> friends = new LinkedList<UserMessage>();

			for (String s : this.m_Database.getFriendID(f.getRFID())) {
				// Get user details from list of RFIDs.
				friends.add(this.m_Database.getUserDetails(s));
			}

			f.setFriendList(friends);

			if (f != null)
				sendMessage(f);

		} else if (m instanceof PendingFriendRequestsMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new PendingFriendRequestsMessage.");

			PendingFriendRequestsMessage f = (PendingFriendRequestsMessage)m;
			List<UserMessage> requests = new LinkedList<UserMessage>();

			for (String s : this.m_Database.getFriendRequests(f.getRFID())) {
				// Get user details from list of RFIDs.
				requests.add(this.m_Database.getUserDetails(s));
			}

			f.setFriendRequests(requests);

			if (f != null)
				sendMessage(f);

		} else if (m instanceof RFIDMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new RFIDMessage.");

			// We get an RFID, is it a User or a Location?
			// Send back the appropriate response.
			Message answer = this.m_Database.checkRFID(m.getRFID());
			if (answer != null)
				sendMessage(answer);

		} else if (m instanceof CheckInMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new CheckInMessage.");

			// Record the check in.
			CheckInMessage c = (CheckInMessage)m;
			this.m_Database.checkIn(c.getRFID(), c.getAttractionID(), c.getCheckInTime());

		} else if (m instanceof FriendCheckInMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new FriendCheckInMessage.");
			
			FriendCheckInMessage f = (FriendCheckInMessage) m;
			
			String rfid;
			List<UserMessage> friends = new LinkedList<UserMessage>();
		
			if ((rfid = f.getFriendRFID()) == null) {
				
				for (String s : this.m_Database.getFriendID(f.getRFID())) {
					// Get user details from list of RFIDs.
					friends.add(this.m_Database.getUserDetails(s));
				}
				
			} else {
				friends.add(this.m_Database.getUserDetails(rfid));
			}
			
			List<CheckInMessage> friendCheckIns = new LinkedList<CheckInMessage>();
			
			for (UserMessage u : friends) {
				for (CheckInMessage c : this.m_Database.getCheckIns(u.getRFID())) {
					c.setName(u.toString());
					friendCheckIns.add(c);
				}
			}
			
			f.setFriendCheckIns(friendCheckIns);
			
			if (f != null)
				sendMessage(f);

		} else if (m instanceof SecurityMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new SecurityMessage.");
			// Get security details - name, type, check-ins for the RFID.
			SecurityMessage s = this.m_Database.getSecurityDetails(m.getRFID());

			if (s != null) {
				sendMessage(s);
			}
		} else if (m instanceof UserTableMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new UserTableMessage.");
			
			UserTableMessage u = (UserTableMessage) m;
			u.setList(this.m_Database.getUsers());
			
			if (u != null) {
				sendMessage(u);
			}
		} else if (m instanceof LocationTableMessage) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Recieved new LocationTableMessage.");
			
			LocationTableMessage l = (LocationTableMessage) m;
			l.setList(this.m_Database.getLocations());
			
			if (l != null) {
				sendMessage(l);
			}
		}
	}

	@Override
	public void run() {
		try {
			// Input stream for objects from client.
			ObjectInputStream objectInputStream = new ObjectInputStream(this.m_Socket.getInputStream());

			Object o;
			while (true) {
				o = null;
				try {
					// Block until the client sends an object.
					o = objectInputStream.readObject();

					decodeMessage((Message)o);

				} catch (ClassNotFoundException e) {
					e.printStackTrace();

				} catch (SQLException e) {
					e.printStackTrace();

				}

			}

		} catch (IOException e) {
			System.out.println(MayBallServer.DateFormat.format(new Date()) + " Client: " + this.m_Socket.getInetAddress().getHostAddress() +
					" disconnected from port " + this.m_Socket.getPort() + ".");
		}

	}

	/**
	 * Put a message on the queue for sending to the client.
	 * @param m The message to be sent.
	 */
	private void sendMessage(Message m) {
		this.m_Messages.put(m);
	}

}
